package com.wanwu.channel.remote.allinpay.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.wanwu.channel.common.enums.ExceptionEnum;
import com.wanwu.channel.common.enums.TradeStatusEnum;
import com.wanwu.channel.common.exception.SystemException;
import com.wanwu.channel.common.util.LogUtil;
import com.wanwu.channel.common.util.SybUtil;
import com.wanwu.channel.remote.allinpay.config.TongLianConfig;
import com.wanwu.channel.remote.allinpay.http.req.TLBaseReq;
import com.wanwu.channel.remote.allinpay.http.req.TLPrepayReq;
import com.wanwu.channel.remote.allinpay.http.req.TLQueryReq;
import com.wanwu.channel.remote.allinpay.http.req.TLRefundReq;
import com.wanwu.channel.remote.allinpay.http.resp.*;
import com.wanwu.channel.remote.allinpay.http.util.HttpConnectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.TreeMap;

/**
 * @ClassName TonglianHttp
 * @Description 通联支付接口
 * @Author husheng
 * @Date 2020-01-15 20:26
 */
@Component
public class TongLianHttp {

    private static Logger logger = LoggerFactory.getLogger(TongLianHttp.class);

    @Autowired
    private TongLianConfig tongLianConfig;

    /**
     * 通联接口枚举
     */
    public enum InterEnum {
        PREPAY("统一支付", "pay"),
        PAY_QUERY("交易查询","query"),
        REFUND_QUERY("退款查询","query"),
        APPLY_REFUND("交易退款", "refund"),
        H5PAY("H5支付", "h5unionpay/unionorder");

        private String interName;

        private String code;

        InterEnum(String interName, String code) {
            this.interName = interName;
            this.code = code;
        }

        public String getInterName() {
            return interName;
        }

        public String getCode() {
            return code;
        }

        public boolean isPrepay(){
            return this.equals(InterEnum.PREPAY);
        }

        public boolean isQuery(){
            return this.equals(InterEnum.PAY_QUERY) || this.equals(InterEnum.REFUND_QUERY);
        }
    }

    /**
     * 通联统一支付
     * @param req
     * @return
     */
    public TLPrepayResp prepay(TLPrepayReq req){
        return doHttpsPost(req, TLPrepayResp.class, InterEnum.PREPAY);
    }

    /**
     * 通联H5支付
     * @param req
     * @return
     */
    public TLH5payResp h5pay(TLPrepayReq req){
        return buildH5Url(req, InterEnum.H5PAY);
    }

    /**
     * 通联交易查询
     * @param req
     * @return
     */
    public TLQueryResp query(TLQueryReq req){
        return doHttpsPost(req, TLQueryResp.class, InterEnum.PAY_QUERY);
    }

    public TLQueryResp queryRefund(TLQueryReq req){
        return doHttpsPost(req, TLQueryResp.class, InterEnum.REFUND_QUERY);
    }

    /**
     * 通联交易退款
     * @param req
     * @return
     */
    public TLRefundResp refund(TLRefundReq req){
        return doHttpsPost(req, TLRefundResp.class, InterEnum.APPLY_REFUND);
    }

    /**
     * 进行htts调用
     * @param req
     * @param inter
     * @param <R>
     * @param <T>
     */
    private <R extends TLBaseReq, T extends TLBaseResp> T doHttpsPost(R req, Class<T> cla, InterEnum inter){
        HttpConnectionUtil http = new HttpConnectionUtil(tongLianConfig.getApiUrl() + "/" + inter.getCode());
        try {
            http.init();
            //组公共请求头
            buildBaseReq(req);
            PropertyPreFilters filters = new PropertyPreFilters();
            PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
            String[] excludeProperties = {"channelMerchantInfoEntity"};
            excludefilter.addExcludes(excludeProperties);
            String requestStr = JSON.toJSONString(req, excludefilter);
            String appKey = tongLianConfig.getAppKey();
            if(null != req.getChannelMerchantInfoEntity()){
                appKey = req.getChannelMerchantInfoEntity().getApiKey();
            }
            //排序
            TreeMap<String, String> params = SybUtil.json2Obj(requestStr, TreeMap.class);
            LogUtil.info(logger, "请求通联{0}接口，签名前数据：{1}", inter.getInterName(), params);
            //签名
            String sign = SybUtil.sign(params, appKey);
            LogUtil.info(logger, "请求通联{0}接口，签名：{1}", inter.getInterName(), sign);
            params.put("sign", sign);
            //发送请求报文并获得返回
            long start = System.currentTimeMillis();
            byte[] bys = http.postParams(params, true);
            if(bys == null || bys.length == 0){
                throw new SystemException(ExceptionEnum.TL1000);
            }
            String result = new String(bys,"UTF-8");
            LogUtil.info(logger, "请求通联{0}接口，返回数据：{1}，耗时:{2}", inter.getInterName(), result, System.currentTimeMillis()-start);
            //验证签名
            if(!verifySign(result)){
                throw new SystemException(ExceptionEnum.TL1002);
            }

            //需要保存渠道支付返回的报文
            if(inter.isPrepay()){
                TLPrepayResp tlPrepayResp = (TLPrepayResp)JSONObject.parseObject(result, cla);
                tlPrepayResp.setPrePayReq(http.getUrlParams());
                tlPrepayResp.setPrePayResp(result);
                return (T) tlPrepayResp;
            }

            //海关总署-通联H5支付方式返回报文，落的是查询或者回调报文终态
            if (inter.isQuery()) {
                TLQueryResp tlQueryResp = (TLQueryResp)JSONObject.parseObject(result, cla);
                tlQueryResp.setQueryResp(result);
                return (T) tlQueryResp;
            }

            return JSONObject.parseObject(result, cla);
        } catch (Exception e){
            LogUtil.error(logger, e, "请求通联" + inter.getInterName() + "接口异常");
            if(e instanceof SystemException){
                throw (SystemException)e;
            }else{
                //超时固定编码，请勿改动错误编码
                throw new SystemException(ExceptionEnum.TIME_OUT);
            }
        }finally {
            //释放链接
            http.destory();
        }
    }

    /**
     * 拼接H5 url(未请求通联)
     * @param req
     * @param inter
     * @param <R>
     */
    private <R extends TLBaseReq> TLH5payResp buildH5Url(R req, InterEnum inter) {
        try {
            buildBaseReq(req);
            PropertyPreFilters filters = new PropertyPreFilters();
            PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
            String[] excludeProperties = {"channelMerchantInfoEntity"};
            excludefilter.addExcludes(excludeProperties);
            String requestStr = JSON.toJSONString(req, excludefilter);
            //排序
            TreeMap<String, String> params = SybUtil.json2Obj(requestStr, TreeMap.class);
            LogUtil.info(logger, "请求通联{0}接口，签名前数据：{1}", inter.getInterName(), params);
            //签名
            String sign = SybUtil.sign(params, tongLianConfig.getAppKey());
            LogUtil.info(logger, "请求通联{0}接口，签名：{1}", inter.getInterName(), sign);
            params.put("sign", sign);

            //拿到拼装后的请求参数，用于提供给海关总署查询使用
            String urlParams = buildUrl(params);
            String h5PayUrl = tongLianConfig.getApiH5Url() + inter.getCode() + "?" + urlParams;

            TLH5payResp tlh5payResp = new TLH5payResp();
            tlh5payResp.setPrePayReq(urlParams);
            tlh5payResp.setMWebUrl(h5PayUrl);
            return tlh5payResp;
        } catch (Exception e){
            LogUtil.error(logger, e, "拼接通联" + inter.getInterName() + "接口异常");
            if(e instanceof SystemException){
                throw (SystemException)e;
            }else{
                //超时固定编码，请勿改动错误编码
                throw new SystemException(ExceptionEnum.TIME_OUT);
            }
        }
    }

    private String buildUrl(Map<String, String> params) throws UnsupportedEncodingException {
        StringBuilder outBuf = new StringBuilder();
        boolean isNotFirst = false;
        for (Map.Entry<String, String> entry: params.entrySet()){
            if (isNotFirst)
                outBuf.append('&');
            isNotFirst = true;
            outBuf.append(entry.getKey())
                    .append('=')
                    .append(URLEncoder.encode(entry.getValue(), "UTF-8"));
        }
        return outBuf.toString();
    }

    /**
     * 验证签名
     * @param result
     * @return
     */
    private boolean verifySign(String result){
        //处理返回内容
        TreeMap<String, String> resultMap = SybUtil.json2Obj(result, TreeMap.class);
        //验签
        if("SUCCESS".equals(resultMap.get("retcode"))){
            String returnSign = resultMap.get("sign");
            try {
                String localSign = SybUtil.sign(resultMap, tongLianConfig.getAppKey());
                if(returnSign.toLowerCase().equals(localSign.toLowerCase())){
                    return true;
                }
            }catch (Exception e){
                return false;
            }
        }else{
            throw new SystemException(ExceptionEnum.E10001, resultMap.get("retmsg"), TradeStatusEnum.FAIL);
        }
        return false;
    }

    /**
     * 组公共请求头
     * @param req
     * @param <R>
     */
    private <R extends TLBaseReq> void buildBaseReq(R req) {
        //目前默认11
        if (ObjectUtils.isEmpty(req.getVersion())) {
            req.setVersion("11");
        }
        req.setRandomStr(SybUtil.getValidateCode(16));
    }

    public static void main(String[] args) {
        String result = "{\"appid\":\"00184192\",\"chnltrxid\":\"122038040000206168\",\"cusid\":\"56039304816WJZX\",\"payinfo\":\"{\\\"appId\\\":\\\"wx714e282c4527e05e\\\",\\\"timeStamp\\\":\\\"1582358713\\\",\\\"nonceStr\\\":\\\"f5ce815a647b4e05bc9cc04b212dfbb5\\\",\\\"package\\\":\\\"prepay_id=wx2216051332748169d72d56e11415494500\\\",\\\"signType\\\":\\\"RSA\\\",\\\"paySign\\\":\\\"luH5U8/bxBiMJUKx1z2vEGnrdswV5HPdf9bOVePxvKOPf81maw+a92LhAkdmN3AezIWwMYXTZUYJN4tQZe3+huxjAMWs9EzXIEtxm6QuvXG8BzvKjjdCOksQ+bYpoFTimk6wc/BzHXrQKsoTlCTJqKGBTFCcPAAJwkK+/ZU5dCI2SuA2nEU2I/dujxEK4P2AZB6iO3hUDPhjKz7N8NtHSsosaiUg6rzYXUjRDELXu7Ia/+vdNnRqjS7jhzoaKdPox7u2wkWbKvUm8yeE663XFT7dKiNs+PeuUQi9vgmG9bexT1VlvAVkU37t4/thPU2Kr5a9taPcDwVYEuAGeqookQ==\\\"}\",\"randomstr\":\"669264997313\",\"reqsn\":\"158235871302116651\",\"retcode\":\"SUCCESS\",\"sign\":\"0035700810C4750FBBF632140F72CF33\",\"trxid\":\"122038040000206168\",\"trxstatus\":\"0000\",\"commonReq\":\"0000\"}";
        System.out.println(JSONObject.parseObject(result, TLPrepayResp.class).toString());

    }
}
